/*
1. Ban list for a given instance, should be optional
2. The big fat collection as parameter, client responsibility to parse that collection and decide what to do when a new response is add it
3. Method that notify when one new source has been resolved... future
 */
package org.biodas.jdas.client.threads;

import org.apache.log4j.Logger;
import org.biodas.jdas.client.FeaturesClient;
import org.biodas.jdas.client.adapters.features.DasGFFAdapter;
import org.biodas.jdas.exceptions.DASClientException;
import org.biodas.jdas.exceptions.ErrorInSource;

import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: ljgarcia
 * Date: 30/11/11
 * Time: 14:50
 * To change this template use File | Settings | File Templates.
 */
public class FeaturesClientMultipleSources extends ClientMultipleSources {
    private static Logger logger = Logger.getLogger(FeaturesClientMultipleSources.class);

    /**
     * Default constructor, it defines an initial pool with 5 threads, a maximum of 10 threads,
     * and a keepAlive time of 5 seconds.
     */
    public FeaturesClientMultipleSources() {
        super();
    }

    /**
     * Constructor with parameters, it enables to define the initial pool size, maximum pool size,
     * and keep alive time in seconds; it initializes the ThreadPoolExecutor.
     *
     * @param poolSize      Initial pool size
     * @param maxPoolSize   Maximum pool size
     * @param keepAliveTime Keep alive time in seconds
     */
    public FeaturesClientMultipleSources(int poolSize, int maxPoolSize, long keepAliveTime) {
        super(poolSize, maxPoolSize, keepAliveTime);
    }

    /**
     * Constructor with parameters, it gets an Observer for this object and add it to the observers collection;
     * it also defines an initial pool with 5 threads, a maximum of 10 threads, and a keepAlive time of 5 seconds.
     *
     * @param observer A new observer for this object
     */
    public FeaturesClientMultipleSources(Observer observer) {
        super(observer);
    }

    /**
     * Constructor with parameters, it enables to define the initial pool size, maximum pool size,
     * and keep alive time in seconds; it initializes the ThreadPoolExecutor.
     *
     * @param poolSize      Initial pool size
     * @param maxPoolSize   Maximum pool size
     * @param keepAliveTime Keep alive time in seconds
     * @param observer      A new observer for this object
     */
    public FeaturesClientMultipleSources(int poolSize, int maxPoolSize, long keepAliveTime, Observer observer) {
        super(poolSize, maxPoolSize, keepAliveTime, observer);
    }

    /**
     * Fetches data from multiple sources for a list of segments; segments can include ranges.
     * The invoking method is responsible for processing the results.
     * @param sources List of sources (url)
     * @param segments List of segments
     * @param results Map of (source name, (list of segments, DASGFF responses)) one for each source successfully queried
     * @param errors  Map of (source name, (list of segments, exceptions)) one for each source raising an exception
     */
	public void fetchData(List<String> sources, final List<String> segments,
			final Map<String, Map<List<String>, DasGFFAdapter>> results,
			final Map<String, Map<List<String>, Exception>> errors) {
		List ids = new ArrayList();
		ids.add(segments);
		fetchData(sources, ids, true, results, errors);
	}
    /**
     * Fetches data from multiple sources, querying each with a list of segments taken from a list of segment lists; segments can include ranges.
     * The invoking method is responsible for processing the results.
     * @param sources List of sources (url)
     * @param segmentsets List of segment lists to query to each source
     * @param reiterate flag to set if the list of segement lists should be iterated over again if it is shorter than the list of sources
     * @param results Map of (source name, (list of segments, DASGFF responses)) one for each source successfully queried
     * @param errors  Map of (source name, (list of segments, exceptions)) one for each source raising an exception
     */
	public void fetchData(List<String> sources, List<List<String>> segmentsets,
			boolean reiterate,
			final Map<String, Map<List<String>, DasGFFAdapter>> results,
			final Map<String, Map<List<String>, Exception>> errors) {
		int sp = 0;
		if ((!reiterate) && (segmentsets.size() != sources.size())) {
			throw new Error(
					"Mismatch between number of sources and number of sets of segments. Please set reiterate flag or provide a segment set for each source");
		}
        final FeaturesClientMultipleSources self = this;
		for (final String source : sources) {
			final List segments = (List) segmentsets.get(sp++);
			if ((reiterate) && (sp == segmentsets.size())) {
				sp = 0;
			}
			this.runTask(new Runnable() {
				@Override
				public void run() {
					try {
						FeaturesClient featuresClient = new FeaturesClient(
								getConnPropsFor(source));
						DasGFFAdapter adapter = featuresClient.fetchData(
								source, segments);
						if (results.get(source) == null) {
							Map<List<String>, DasGFFAdapter> response = new HashMap<List<String>, DasGFFAdapter>();
							response.put(segments, adapter);
							results.put(source, response);
						} else {
							results.get(source).put(segments, adapter);
						}
						logger.debug("Response from " + source
								+ " has been already retrieved");
                        self.changeAndNotify(source);
					} catch (DASClientException e) {
						if (errors.get(source) == null) {
							Map<List<String>, Exception> response = new HashMap<List<String>, Exception>();
							response.put(segments, e);
							errors.put(source, response);
						} else {
							errors.get(source).put(segments, e);
						}
						logger.error("Fetching data from source " + source
								+ " raised an exception: " + e.getMessage());
                        self.changeAndNotify(new ErrorInSource(source, e));
					}
				}
			});
		}
	}

    /**
     * Fetch data from multiple sources for a list of segments; segments can include ranges.
     * The invoking method is responsible for processing the results.
     * Please note that results are identified by source and segments, it is not advisable to use the same instance to
     * query the same sources with the same segments but different methods as the results will be replaced.
     * @param sources List of sources (url)
     * @param segments List of segments
     * @param types List of types
     * @param results Map of (source name, (list of segments, DASGFF responses)) one for each source successfully queried
     * @param errors  Map of (source name, (list of segments, exceptions)) one for each source raising an exception
     */
    public void fetchDataWithTypes(List<String> sources, final List<String> segments, final List<String>types, final Map<String, Map<List<String>, DasGFFAdapter>> results, final Map<String, Map<List<String>, Exception>> errors) {
        final FeaturesClientMultipleSources self = this;
        for (final String source: sources) {
            this.runTask(new Runnable() {
                @Override
                public void run() {
                    try {
                        FeaturesClient featuresClient = new FeaturesClient(getConnPropsFor(source));
                        DasGFFAdapter adapter = featuresClient.fetchDataWithTypes(source, segments, types);
                        if (results.get(source) == null) {
                            Map<List<String>, DasGFFAdapter> response = new HashMap<List<String>, DasGFFAdapter>();
                            response.put(segments, adapter);
                            results.put(source, response);
                        } else {
                            results.get(source).put(segments, adapter);
                        }
                        logger.debug("Response from " + source + " has been already retrieved");
                        self.changeAndNotify(source);
                    } catch (DASClientException e) {
                        if (errors.get(source) == null) {
                            Map<List<String>, Exception> response = new HashMap<List<String>, Exception>();
                            response.put(segments, e);
                            errors.put(source, response);
                        } else {
                            errors.get(source).put(segments, e);
                        }
                        logger.error("Fetching data from source " + source + " raised an exception: " + e.getMessage());
                        self.changeAndNotify(new ErrorInSource(source, e));
                    }
                }
            });
        }
    }

    /**
     * Fetch data from multiple sources for a list of segments; segments can include ranges.
     * The invoking method is responsible for processing the results.
     * Please note that results are identified by source and segments, it is not advisable to use the same instance to
     * query the same sources with the same segments but different methods as the results will be replaced.
     * @param sources List of sources (url)
     * @param segments List of segments
     * @param featureIds List of feature ids
     * @param results Map of (source name, (list of segments, DASGFF responses)) one for each source successfully queried
     * @param errors  Map of (source name, (list of segments, exceptions)) one for each source raising an exception
     */
    public void fetchDataWithFeatueIds(List<String> sources, final List<String> segments, final List<String> featureIds, final Map<String, Map<List<String>, DasGFFAdapter>> results, final Map<String, Map<List<String>, Exception>> errors) {
        final FeaturesClientMultipleSources self = this;
        for (final String source: sources) {
            this.runTask(new Runnable() {
                @Override
                public void run() {
                    try {
                        FeaturesClient featuresClient = new FeaturesClient(getConnPropsFor(source));
                        DasGFFAdapter adapter = featuresClient.fetchDataWithFeatueIds(source, segments, featureIds);
                        if (results.get(source) == null) {
                            Map<List<String>, DasGFFAdapter> response = new HashMap<List<String>, DasGFFAdapter>();
                            response.put(segments, adapter);
                            results.put(source, response);
                        } else {
                            results.get(source).put(segments, adapter);
                        }
                        logger.debug("Response from " + source + " has been already retrieved");
                        self.changeAndNotify(source);
                    } catch (DASClientException e) {
                        if (errors.get(source) == null) {
                            Map<List<String>, Exception> response = new HashMap<List<String>, Exception>();
                            response.put(segments, e);
                            errors.put(source, response);
                        } else {
                            errors.get(source).put(segments, e);
                        }
                        logger.error("Fetching data from source " + source + " raised an exception: " + e.getMessage());
                        self.changeAndNotify(new ErrorInSource(source, e));
                    }
                }
            });
        }
    }

    /**
     /**
     * Fetch data from multiple sources for a list of segments; segments can include ranges.
     * The invoking method is responsible for processing the results.
     * Please note that results are identified by source and segments, it is not advisable to use the same instance to
     * query the same sources with the same segments but different methods as the results will be replaced.
     * @param sources List of sources (url)
     * @param segments List of segments
     * @param types List of types
     * @param categories List of categories
     * @param categorize
     * @param featureIds List of feature ids
     * @param maxbins
     * @param results Map of (source name, (list of segments, DASGFF responses)) one for each source successfully queried
     * @param errors  Map of (source name, (list of segments, exceptions)) one for each source raising an exception
     */
    public void fetchData(List<String> sources, final List<String> segments, final List<String> types
        , final List<String> categories, final String categorize, final List<String> featureIds, final String maxbins
        , final Map<String, Map<List<String>, DasGFFAdapter>> results, final Map<String, Map<List<String>, Exception>> errors) {
        final FeaturesClientMultipleSources self = this;
        for (final String source: sources) {
            this.runTask(new Runnable() {
                @Override
                public void run() {
                    try {
                        FeaturesClient featuresClient = new FeaturesClient(getConnPropsFor(source));
                        DasGFFAdapter adapter = featuresClient.fetchData(source, segments, types, categories, categorize, featureIds, maxbins);
                        if (results.get(source) == null) {
                            Map<List<String>, DasGFFAdapter> response = new HashMap<List<String>, DasGFFAdapter>();
                            response.put(segments, adapter);
                            results.put(source, response);
                        } else {
                            results.get(source).put(segments, adapter);
                        }
                        logger.debug("Response from " + source + " has been already retrieved");
                        self.changeAndNotify(source);
                    } catch (DASClientException e) {
                        if (errors.get(source) == null) {
                            Map<List<String>, Exception> response = new HashMap<List<String>, Exception>();
                            response.put(segments, e);
                            errors.put(source, response);
                        } else {
                            errors.get(source).put(segments, e);
                        }
                        logger.error("Fetching data from source " + source + " raised an exception: " + e.getMessage());
                        self.changeAndNotify(new ErrorInSource(source, e));
                    }
                }
            });
        }
    }
}
